using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dokan;
using MediaAssistant.DAL;

namespace MediaFS
{
    public class MediaFSMirror : IMediaFSOperations
    {
        private int _count;
        private Dictionary<string, string> FilePathMap { get; set; }
        private Dictionary<string, LibraryItem> LibraryItemPathMap { get; set; }
        public MediaFSMirror()
        {
            _count = 1;
            FilePathMap = new Dictionary<string, string>();
            LibraryItemPathMap = new Dictionary<string, LibraryItem>();
            FileInformationCache=new Dictionary<string, FileInformation>();
            FindFilesCacne=new Dictionary<string, ArrayList>();
        }

        private string GetPath(string filename)
        {
            if (FilePathMap.ContainsKey(filename) == false)
            {
                lock (DatabaseManager.LockObject)
                {
                    if (FilePathMap.ContainsKey(filename) == false)
                    {
                        var fileInfo = new FileInfo(filename);
                        var libraryItem = GetLibraryItem(filename);
                        if(libraryItem!=null)
                        {
                            if (Utility.IsMusicFile(filename))
                            {
                                var music = libraryItem.Musics.FirstOrDefault(m => IsSameFile(fileInfo, m.FileName));
                                FilePathMap.Add(filename, music.FullPath);
                            }
                            else if (Utility.IsMovieFile(filename))
                            {
                                var movie = libraryItem.Movies.FirstOrDefault(m => IsSameFile(fileInfo, m.FileName));
                                FilePathMap.Add(filename, movie.GetFileToPlay());
                            }
                            else
                                FilePathMap.Add(filename, filename);    
                        }
                        
                    }
                }
            }
            return FilePathMap.ContainsKey(filename) ? FilePathMap[filename] : string.Empty;
        }

        private LibraryItem GetLibraryItem(string filename)
        {
            if (LibraryItemPathMap.ContainsKey(filename))
            {
                return LibraryItemPathMap[filename];
            }
            var tokens = filename.Split("\\".ToCharArray()).Where(t => string.IsNullOrWhiteSpace(t) == false);
            var items = DatabaseManager.Instance.GetRootLibraryItems().ToArray();
            LibraryItem item=null;
            foreach (var token in tokens)
            {
                var title = token;
                var temp = items.FirstOrDefault(o => o.Title.Equals(title, StringComparison.InvariantCultureIgnoreCase));
                if (temp == null)
                    break;
                item = temp;
                items = item.Children.ToArray();
            }
            if (item != null)
            {
                LibraryItemPathMap.Add(filename, item);
            }
            return item;
        }
        private static bool IsSameFile(FileInfo fileInfo, string fileName)
        {
            return fileName != null && fileName.Equals(fileInfo.Name, StringComparison.InvariantCultureIgnoreCase);
        }

        public int CreateFile(String filename, FileAccess access, FileShare share, FileMode mode, FileOptions options, DokanFileInfo info)
        {
            var path = GetPath(filename);
            info.Context = _count++;
            if (File.Exists(path))
            {
                return 0;
            }
            var libraryItem = GetLibraryItem(filename);
            if (libraryItem!=null)
            {
                info.IsDirectory = true;
                return 0;
            }
            return -DokanNet.ERROR_FILE_NOT_FOUND;
        }

        public int OpenDirectory(String filename, DokanFileInfo info)
        {
            info.Context = _count++;
            var libraryItem = GetLibraryItem(filename);
            if (libraryItem != null || "\\".Equals(filename))
                return 0;
            return -DokanNet.ERROR_PATH_NOT_FOUND;
        }

        public int CreateDirectory(String filename, DokanFileInfo info)
        {
            return -1;
        }

        public int Cleanup(String filename, DokanFileInfo info)
        {
            return 0;
        }

        public int CloseFile(String filename, DokanFileInfo info)
        {
            return 0;
        }

        public int ReadFile(String filename, Byte[] buffer, ref uint readBytes, long offset, DokanFileInfo info)
        {
            try
            {
                var fs = File.OpenRead(GetPath(filename));
                fs.Seek(offset, SeekOrigin.Begin);
                readBytes = (uint)fs.Read(buffer, 0, buffer.Length);
                return 0;
            }
            catch (Exception)
            {
                return -1;
            }
        }

        public int WriteFile(String filename, Byte[] buffer, ref uint writtenBytes, long offset, DokanFileInfo info)
        {
            return -1;
        }

        public int FlushFileBuffers(String filename, DokanFileInfo info)
        {
            return -1;
        }

        public int GetFileInformation(String filename, FileInformation fileinfo, DokanFileInfo info)
        {
            var fileInformation = GetFileInformation(filename, null);
            if(fileInformation!=null)
            {
                fileinfo.Attributes = fileInformation.Attributes;
                fileinfo.CreationTime = fileInformation.CreationTime;
                fileinfo.LastAccessTime = fileInformation.LastAccessTime;
                fileinfo.LastWriteTime = fileInformation.LastWriteTime;
                fileinfo.Length = fileInformation.Length;
                fileinfo.FileName = fileInformation.FileName;
                return 0;    
            }
            
            //if (filename == "\\")
            //{
            //    fileinfo.Attributes = FileAttributes.Directory;
            //    fileinfo.LastAccessTime = DateTime.Now;
            //    fileinfo.LastWriteTime = DateTime.Now;
            //    fileinfo.CreationTime = DateTime.Now;
            //    return 0;
            //}
            //var path = GetPath(filename);
            //if (File.Exists(path))
            //{
            //    var f = new FileInfo(path);
            //    fileinfo.Attributes = f.Attributes;
            //    fileinfo.CreationTime = f.CreationTime;
            //    fileinfo.LastAccessTime = f.LastAccessTime;
            //    fileinfo.LastWriteTime = f.LastWriteTime;
            //    fileinfo.Length = f.Length;
            //    return 0;
            //}
            //var libraryItem = GetLibraryItem(path);
            //if (libraryItem!=null)
            //{
            //    var f = new DirectoryInfo(path);
            //    fileinfo.Attributes = f.Attributes;
            //    fileinfo.CreationTime = f.CreationTime;
            //    fileinfo.LastAccessTime = f.LastAccessTime;
            //    fileinfo.LastWriteTime = f.LastWriteTime;
            //    fileinfo.Length = 0;// f.Length;
            //    return 0;
            //}
            return -1;
        }

        private Dictionary<string, ArrayList> FindFilesCacne { get; set; } 
        public int FindFiles(String filename, ArrayList files, DokanFileInfo info)
        {
            if(FindFilesCacne.ContainsKey(filename)==false)
            {
                lock (DatabaseManager.LockObject)
                {
                    if (filename == "\\")
                    {
                        foreach (var item in DatabaseManager.Instance.GetRootLibraryItems())
                        {
                            var fileInformation = GetFileInformation(item.Title, item);
                            if (fileInformation != null)
                                files.Add(fileInformation);
                        }
                        FindFilesCacne.Add(filename,files);
                        return 0;
                    }
                    var libraryItem = GetLibraryItem(filename);
                    if (libraryItem != null)
                    {
                        AddFileInfo(libraryItem, files);
                        FindFilesCacne.Add(filename, files);
                        return 0;
                    }
                    return -1;
                }    
            }
            foreach (var file in FindFilesCacne[filename])
            {
                files.Add(file);
            }
            return 0;

        }
        private void AddFileInfo(LibraryItem item, ArrayList files)
        {
            lock (DatabaseManager.LockObject)
            {
                if(item.HasChildren==true)
                {
                    foreach (var libraryItem in item.Children)
                    {
                        var fileInformation = GetFileInformation(libraryItem.Title, libraryItem);
                        if(fileInformation!=null)
                            files.Add(fileInformation);
                    }
                    return;
                }
                
                if(item.Musics.Count>0)
                {
                    foreach (var music in item.Musics.Where(m => string.IsNullOrWhiteSpace(m.FullPath) == false))
                    {
                        if (File.Exists(music.FullPath))
                        {
                            var fileInformation = GetFileInformation(music.FileName, null);
                            if(fileInformation!=null)
                                files.Add(fileInformation);
                        }
                    }
                    return;
                }

                if(item.Movies.Count>0)
                {
                    AddMovieFiles(files, item.Movies);
                }
            }
        }
        private void AddMovieFiles(ArrayList files, IEnumerable<Movie> movies)
        {
            foreach (var movie in movies)
            {
                var fileToPlay = movie.GetFileToPlay();
                if(string.IsNullOrEmpty(fileToPlay)==false && File.Exists(fileToPlay))
                {
                    var fileInformation = GetFileInformation(fileToPlay, null);
                    if(fileInformation!=null)
                        files.Add(fileInformation);
                }
            }
        }

        private Dictionary<string, FileInformation> FileInformationCache { get; set; }
 
        private FileInformation GetFileInformation(string fileName, LibraryItem libraryItem)
        {
            if (FileInformationCache.ContainsKey(fileName) == false)
            {
                if (fileName == "\\")
                {
                    FileInformationCache.Add(fileName, CreateDirectoryInformation(string.Empty));
                }
                else if (File.Exists(fileName))
                {
                    FileInformationCache.Add(fileName, CreateFileInformation(fileName));
                }
                else
                {
                    if(libraryItem==null)
                        libraryItem = GetLibraryItem(fileName);
                    if(libraryItem!=null)
                        FileInformationCache.Add(fileName, CreateDirectoryInformation(libraryItem.Title));
                    else
                    {
                        return null;
                    }
                }
            }
            return FileInformationCache[fileName];
        }

        private static FileInformation CreateFileInformation(string fileName)
        {
            var fileInfo = new FileInfo(fileName);
            var finfo = new FileInformation
                            {
                                FileName = fileInfo.Name,
                                Attributes = fileInfo.Attributes,
                                LastAccessTime = fileInfo.LastAccessTime,
                                LastWriteTime = fileInfo.LastWriteTime,
                                CreationTime = fileInfo.CreationTime,
                                Length = fileInfo.Length
                            };
            return finfo;
        }

        private static FileInformation CreateDirectoryInformation(string fileName)
        {
            var finfo = new FileInformation
                            {
                                Attributes = FileAttributes.Directory,
                                LastAccessTime = DateTime.Now,
                                LastWriteTime = DateTime.Now,
                                CreationTime = DateTime.Now,
                                Length = 0
                            };
            if (string.IsNullOrEmpty(fileName) == false)
                finfo.FileName = fileName;
            return finfo;
        }

        public int SetFileAttributes(String filename, FileAttributes attr, DokanFileInfo info)
        {
            return -1;
        }

        public int SetFileTime(String filename, DateTime ctime, DateTime atime, DateTime mtime, DokanFileInfo info)
        {
            return -1;
        }

        public int DeleteFile(String filename, DokanFileInfo info)
        {
            return -1;
        }

        public int DeleteDirectory(String filename, DokanFileInfo info)
        {
            return -1;
        }

        public int MoveFile(String filename, String newname, bool replace, DokanFileInfo info)
        {
            return -1;
        }

        public int SetEndOfFile(String filename, long length, DokanFileInfo info)
        {
            return -1;
        }

        public int SetAllocationSize(String filename, long length, DokanFileInfo info)
        {
            return -1;
        }

        public int LockFile(String filename, long offset, long length, DokanFileInfo info)
        {
            return 0;
        }

        public int UnlockFile(String filename, long offset, long length, DokanFileInfo info)
        {
            return 0;
        }

        public int GetDiskFreeSpace(ref ulong freeBytesAvailable, ref ulong totalBytes, ref ulong totalFreeBytes, DokanFileInfo info)
        {
            freeBytesAvailable = 512 * 1024 * 1024;
            totalBytes = 1024 * 1024 * 1024;
            totalFreeBytes = 512 * 1024 * 1024;
            return 0;
        }

        public int Unmount(DokanFileInfo info)
        {
            return 0;
        }

        public void Initialize(IWaitScreenService waitScreenService)
        {
            
        }
    }
}